
Gaps are quite common in trajectories. For example, GPS tracks may contain gaps if moving objects enter tunnels where GPS reception is lost. In other use cases, moving objects may leave the observation area for longer time before returning and continuing their recorded track.
Depending on the use case, we therefore might want to split trajectories at observation gaps that exceed a certain minimum duration:
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas
import matplotlib.pyplot as plt
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts
import warnings
warnings.filterwarnings('ignore')
plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
opts.defaults(opts.Overlay(active_tools=['wheel_zoom'], frame_width=500, frame_height=400))
mpd.show_versions()
MovingPandas 0.15.rc1 SYSTEM INFO ----------- python : 3.9.15 | packaged by conda-forge | (main, Nov 22 2022, 08:39:05) [MSC v.1929 64 bit (AMD64)] executable : H:\miniconda3\envs\mpd-ex\python.exe machine : Windows-10-10.0.19045-SP0 GEOS, GDAL, PROJ INFO --------------------- GEOS : None GEOS lib : None GDAL : 3.5.0 GDAL data dir: None PROJ : 9.0.0 PROJ data dir: H:\miniconda3\pkgs\proj-9.0.0-h1cfcee9_1\Library\share\proj PYTHON DEPENDENCIES ------------------- geopandas : 0.12.2 pandas : 1.5.3 fiona : 1.8.21 numpy : 1.24.1 shapely : 1.8.2 rtree : 1.0.0 pyproj : 3.3.1 matplotlib : 3.6.3 mapclassify: None geopy : 2.3.0 holoviews : 1.14.9 hvplot : 0.8.2 geoviews : 1.9.6 stonesoup : 0.1b11
gdf = read_file('../data/geolife_small.gpkg')
traj_collection = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')
my_traj = traj_collection.trajectories[1]
print(my_traj)
Trajectory 2 (2009-06-29 07:02:25 to 2009-06-29 11:13:12) | Size: 897 | Length: 38764.6m Bounds: (116.319212, 39.971703, 116.592616, 40.082514) LINESTRING (116.590957 40.071961, 116.590905 40.072007, 116.590879 40.072027, 116.590915 40.072004,
my_traj.plot(column='speed', vmax=20, **plot_defaults)
<AxesSubplot: >
Split the trajectory where then are no observations for at least two minutes:
split = mpd.ObservationGapSplitter(my_traj).split(gap=timedelta(minutes=2))
split
TrajectoryCollection with 5 trajectories
split.to_traj_gdf()
| traj_id | start_t | end_t | geometry | length | direction | |
|---|---|---|---|---|---|---|
| 0 | 2_0 | 2009-06-29 07:02:25 | 2009-06-29 07:13:55 | LINESTRING (116.59096 40.07196, 116.59091 40.0... | 2367.642888 | 345.820618 |
| 1 | 2_1 | 2009-06-29 07:16:55 | 2009-06-29 07:17:05 | LINESTRING (116.58690 40.07961, 116.58689 40.0... | 36.188090 | 188.079866 |
| 2 | 2_2 | 2009-06-29 07:29:35 | 2009-06-29 08:20:15 | LINESTRING (116.58703 40.07951, 116.58704 40.0... | 33766.853732 | 250.896081 |
| 3 | 2_3 | 2009-06-29 10:57:17 | 2009-06-29 11:06:52 | LINESTRING (116.31970 40.00751, 116.31971 40.0... | 1613.488553 | 140.473858 |
| 4 | 2_4 | 2009-06-29 11:09:12 | 2009-06-29 11:10:07 | LINESTRING (116.32636 40.00025, 116.32349 40.0... | 574.481893 | 40.696636 |
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle='round', column='speed', vmax=20)
Split the trajectory where observations stay within 30 meters for at least 1 minute. Discard created trajectories that are shorter than 500 meters long:
split = mpd.StopSplitter(my_traj).split(max_diameter=30, min_duration=timedelta(minutes=1), min_length=500)
split
TrajectoryCollection with 4 trajectories
split.to_traj_gdf()
| traj_id | start_t | end_t | geometry | length | direction | |
|---|---|---|---|---|---|---|
| 0 | 2_2009-06-29 07:06:55 | 2009-06-29 07:06:55 | 2009-06-29 07:12:25 | LINESTRING (116.59258 40.07420, 116.59254 40.0... | 1846.155876 | 331.632126 |
| 1 | 2_2009-06-29 07:29:35 | 2009-06-29 07:29:35 | 2009-06-29 08:02:30 | LINESTRING (116.58703 40.07951, 116.58704 40.0... | 29771.464660 | 244.569782 |
| 2 | 2_2009-06-29 08:07:00 | 2009-06-29 08:07:00 | 2009-06-29 11:04:22 | LINESTRING (116.32328 39.98481, 116.32345 39.9... | 5012.014680 | 11.235899 |
| 3 | 2_2009-06-29 11:06:12 | 2009-06-29 11:06:12 | 2009-06-29 11:13:12 | LINESTRING (116.32741 39.99990, 116.32744 39.9... | 786.228144 | 3.232105 |
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle='round', column='speed', vmax=20)
Split the trajectory where the speed is below one meters per second for at least five minutes:
split = mpd.SpeedSplitter(my_traj).split(speed=1, duration=timedelta(minutes=5))
split
TrajectoryCollection with 3 trajectories
split.to_traj_gdf()
| traj_id | start_t | end_t | geometry | length | direction | |
|---|---|---|---|---|---|---|
| 0 | 2_0 | 2009-06-29 07:02:25 | 2009-06-29 07:17:05 | LINESTRING (116.59096 40.07196, 116.59091 40.0... | 2521.655180 | 336.756427 |
| 1 | 2_1 | 2009-06-29 07:29:55 | 2009-06-29 08:20:15 | LINESTRING (116.58704 40.07947, 116.58705 40.0... | 33662.308829 | 250.906244 |
| 2 | 2_2 | 2009-06-29 10:57:22 | 2009-06-29 11:10:07 | LINESTRING (116.31971 40.00759, 116.31964 40.0... | 2231.112048 | 138.778458 |
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle='round', column='speed', vmax=20)